package app.itgungnir.compose.wan.utils

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.*
import androidx.room.withTransaction
import app.itgungnir.compose.wan.api.WanDao
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import java.io.Serializable

fun ViewModel.launch(
    loadingState: MutableStateFlow<Boolean>? = null,
    errorState: MutableSharedFlow<Throwable?>? = null,
    action: suspend () -> Unit
): Job = viewModelScope.launch {
    errorState?.emit(null)
    loadingState?.value = true
    try {
        action.invoke()
    } catch (e: Exception) {
        e.printStackTrace()
        errorState?.emit(e)
    } finally {
        loadingState?.value = false
    }
}

/**
 * 不需要做数据库缓存的PagingSource封装
 */
fun <T : Serializable> ViewModel.pagingSourceWithoutCache(
    firstPageIndex: Int = 0,
    pageSize: Int = 20,
    networkRequest: suspend (Int) -> WanPagingResponse<T>?
): Flow<PagingData<T>> = Pager(PagingConfig(pageSize = pageSize)) {
    PagingSourceWithoutCache(firstPageIndex = firstPageIndex, networkRequest = networkRequest)
}.flow.cachedIn(viewModelScope)

private class PagingSourceWithoutCache<T : Serializable>(
    private val firstPageIndex: Int = 0,
    private val networkRequest: suspend (Int) -> WanPagingResponse<T>?
) : PagingSource<Int, T>() {
    override fun getRefreshKey(state: PagingState<Int, T>): Int? = null
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> = try {
        val nextPage = params.key ?: firstPageIndex
        val response = try {
            networkRequest.invoke(nextPage)
        } catch (e: Exception) {
            e.printStackTrace()
            null
        }
        if (response != null) {
            LoadResult.Page(
                data = response.datas ?: listOf(),
                prevKey = when (nextPage == 1) {
                    true -> null
                    else -> firstPageIndex.coerceAtLeast(nextPage - 1)
                },
                nextKey = when (response.over) {
                    true -> null
                    else -> response.curPage + 1
                }
            )
        } else {
            LoadResult.Error(IllegalStateException())
        }
    } catch (e: Exception) {
        e.printStackTrace()
        LoadResult.Error(e)
    }
}

/**
 * 需要做数据库缓存的PagingSource封装
 */
@OptIn(ExperimentalPagingApi::class)
fun <T : Serializable> pagingSourceWithCache(
    firstPageIndex: Int = 0,
    pageSize: Int = 20,
    networkRequest: suspend (Int) -> WanPagingResponse<T>?,
    queryCache: (WanDao) -> PagingSource<Int, T>,
    clearCache: suspend (WanDao) -> Unit,
    appendCache: suspend (List<T>, WanDao) -> Unit
): Flow<PagingData<T>> {
    val dao = WanDatabase.dao
    return Pager(
        config = PagingConfig(pageSize = pageSize),
        remoteMediator = PagingSourceWithCache(
            firstPageIndex = firstPageIndex,
            networkRequest = networkRequest,
            clearCache = { clearCache.invoke(dao) },
            appendCache = { appendCache.invoke(it, dao) }
        )
    ) { queryCache.invoke(dao) }.flow
}

@OptIn(ExperimentalPagingApi::class)
private class PagingSourceWithCache<T : Serializable>(
    private val firstPageIndex: Int = 0,
    private val networkRequest: suspend (Int) -> WanPagingResponse<T>?,
    private val clearCache: suspend () -> Unit,
    private val appendCache: suspend (List<T>) -> Unit
) : RemoteMediator<Int, T>() {
    private var currentIndex = firstPageIndex
    override suspend fun load(loadType: LoadType, state: PagingState<Int, T>): MediatorResult {
        return try {
            val nextPage = when (loadType) {
                LoadType.REFRESH -> firstPageIndex.also { currentIndex = it }
                LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
                LoadType.APPEND -> ++currentIndex
            }
            val response = try {
                networkRequest.invoke(nextPage)
            } catch (e: Exception) {
                e.printStackTrace()
                null
            }
            when (response) {
                null -> MediatorResult.Error(IllegalStateException())
                else -> {
                    WanDatabase.instance.withTransaction {
                        if (loadType == LoadType.REFRESH) {
                            clearCache.invoke()
                        }
                        response.datas?.takeIf { it.isNotEmpty() }?.let { list ->
                            appendCache.invoke(list)
                        }
                    }
                    MediatorResult.Success(endOfPaginationReached = response.over)
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
            MediatorResult.Error(e)
        }
    }
}
